//基类 接收参数 负责调度
class Vue {
  // 构造函数
  constructor(options) {
    // 将实例方法属性重新绑定
    this.$el = options.el;
    this.$data = options.data;
    // 判断跟元素是否存在 存在编译模板
    if (this.$el) {

        // 把数据全部转化成Object.defineProperty来定义
        new Observer(this.$data)
        console.log(this.$data);
        

      new Compiler(this.$el, this);
    }
  }
}
/* 将数据和模板进行编译 在将结果重新渲染到页面
    1.获取当前模板
    2.将当前模板塞到内存中
    3.在内存中替换数据
    4.将替换好的模板编译后重新注入页面渲染
*/
class Compiler {
  constructor(el, vm) {
    /*
        构造实例时el可以传字符串 如 el : '#app'
        也可以传递节点 如：el : document.getElementById('#app')  
        判断传入的el属性是不是一个元素，如果不是元素 就获取他
        */
    this.el = this.isElementNode(el) ? el : document.querySelector(el);
    this.vm = vm;

    // 将当前节点中的元素 放入内存中
    let fragment = this.node2fragement(this.el);

    // 把节点中的内容替换

    // 用数据编译模板
    this.compile(fragment);
    // 将替换好的内容重新注入到页面中
    this.el.appendChild(fragment);
  }
  // 判断节点属性KEY是否以'v-'开头
  isDirective(attrName) {
    return attrName.startsWith('v-');
  }

  //编译元素节点
  compileElement(node) {
    //拿到当前元素的属性
    let attributes = node.attributes; //结果是类数组
    [...attributes].forEach(attr => {
      //attr是一个对象 type = "text" k-v形式
      let { name, value: expr } = attr; //解构赋值
      // 判断当前元素是不是包含指令
      if (this.isDirective(name)) {
        // v-model  v-html
        // console.log(node, 'element'); //找到元素
        let [, directive] = name.split('-');
        //对不同指令进行不同处理方法 传入当前节点 当前指令的表达式 实例的数据
        CompileUtil[directive](node, expr, this.vm);
      }
    });
  }

  //编译文本
  compileText(node) {
    //判断当前文本节点中内容是否包含 {{aaa}} {{bbb}}
    let content = node.textContent;

    if (/\{\{(.+?)\}\}/.test(content)) {
      //.+匹配所有内容 ？ 代表到第一个}就停止匹配
      //   console.log(content, 'text'); //找到文本
      CompileUtil['text'](node, content, this.vm); //文本节点编译
    }
  }

  //   核心编译方法
  compile(node) {
    //用来编译内存中的DOM节点
    let childNodes = node.childNodes; //结果是一个类数组
    // 遍历循环数组将结果分类
    [...childNodes].forEach(child => {
      // 判断获取到的当前节点是文本还是元素
      if (this.isElementNode(child)) {
        this.compileElement(child);
        // 如果是元素的话 需要将元素本身传入 再对元素的子节点进行遍历 递归
        this.compile(child);
      } else {
        this.compileText(child);
      }
    });
  }

  // 把节点移动到内存中
  node2fragement(node) {
    //   创建一个内存中的文档碎片
    let fragment = document.createDocumentFragment();
    let firstChild;
    //   将当前节点中的子元素依次创建到文档碎片中
    while ((firstChild = node.firstChild)) {
      /*
          我们使用 JS 从页面获取到的元素对象，或者自己手动创建的已经插入页面的元素对象，与页面中的 HTML 元素是绑定在一起的。也就是说修改其中一个，另一个也会跟着自动修改。这就是 DOM 的映射机制。
          */

      //   appendChild具有移动性
      fragment.appendChild(firstChild);
    }

    return fragment;
  }

  isElementNode(node) {
    //是不是元素节点
    return node.nodeType === 1;
  }
}

CompileUtil = {
  // 根据表达式取到相应的数据 expr表达式
  getVal(vm, expr) {
    //expr.split('.') 为[school,name]
    return expr.split('.').reduce((data, current) => {
      //第一次返回vm.$data.school得到一个对象{name:"珠峰",age:10}
      //第二次返回school.name  返回 "珠峰"
      return data[current];
    }, vm.$data);
  },
  model(node, expr, vm) {
    //node  是节点 expr是表达式 vm是当前实例 从vm.$data中找到和expr表达式对应的值，将它赋给node节点
    let fn = this.updater['modelUpdater'];
    let value = this.getVal(vm, expr); //将vm中对应的值依据表达式赋给node节点 即珠峰

    fn(node, value);
  },
  html(node, expr, vm) {},
  text(node, expr, vm) {
    let fn = this.updater['textUpdater'];
    //用正则匹配文本节点中的内容 更新node文本节点内容
    let content = expr.replace(/\{\{(.+?)\}\}/, (...args) => {
      return this.getVal(vm, args[1]);
    });
    fn(node, content);
  },
//   更新节点内容方法
  updater: {
    //把数据插入到节点中
    modelUpdater(node, value) {
      //更新node的值
      node.value = value;
    },
    htmlUpdater(node, value) {},
    textUpdater(node, value) {
      node.textContent = value;
    }
  }
};


// 实现数据劫持
class Observer{
    constructor(data){
        this.observer(data)        
    }
    
    observer(data){
        // 如果是对象才观察
        if (data && typeof data == 'object') {
            // 如果是对象 循环遍历转化
            for(let key in data){
                this.defineReactive(data,key,data[key])//data对象 key对象的属性名 data[key]属性值
            }
        }
    }
    // 将对象转化成Object.defineProperty形式 添加get和set进行数据监控
    defineReactive(obj,key,value){
        // 如果属性值依然是对象 递归进行劫持
        this.observer(value)
        Object.defineProperty(obj,key,{
            get(){
                return value
            },
            set:(newVal)=>{
                // 判断更新的值与原来的值是否相同
                if (newVal != value) {
                    this.observer(newVal)//如果新值为对象进行数据监控（this使用箭头函数拿到）
                    value = newVal
                }
                
            }
        })
    }
}